﻿#ifndef ___PCLIB_S_INI___
#define ___PCLIB_S_INI___


#include "../m_core/m_core.hpp"



/**
*@brief     配置文件section辅助类，一般情况下用户无需直接使用
*           用户只需要使用下面的CPCConfig类
*/
class CPCConfigSection 
{
public:
    //按照顺序排列的MAP
    typedef std::vector<std::pair<std::string, std::string> >    PC_ORDERED_MAP;
public:
    explicit CPCConfigSection(){  Clear() ;}
    ~CPCConfigSection(){}

    /**
    *@brief        是否为空
    */
    bool IsEmpty() { return m_SectionName.empty() && m_SectionValues.size() == 0;  }

    /**
    *@brief        清理
    */
    void Clear(){m_SectionName = ""; m_SectionValues.clear(); }

    /**
    *@brief        设置section名称
    */
    void SetName(const char* szName) 
    { 
        if(szName != NULL ) m_SectionName = szName;
    }

    /**
    *@brief        添加注释或空行
    */
    void AddComment(const char* szComment)
    {
        if(szComment == NULL)
        {
            return;
        }
        m_SectionValues.push_back(std::make_pair("",szComment));
    }

    /**
    *@brief        添加值
    */
    void AddValue(const char* szLine)
    {
        if(szLine == NULL)
        {
            return ;
        }

        //实际数据
        size_t nEqualPos = 0;
        for (size_t i = 0; i < strlen(szLine); i++)
        {
            if (szLine[i] == '=')
            {
                nEqualPos = i;
                break;
            }
        }
        if ( nEqualPos == 0)
        {
            //没找到等号或等号在第一个字符，当作注释处理
            m_SectionValues.push_back(std::make_pair("",szLine));
            return;
        }
        std::string first = CPCStrUtil::PCStrTrim(szLine, " \n\t\r", (int)nEqualPos );
        std::string second = CPCStrUtil::PCStrTrim(szLine + nEqualPos + 1, " \n\t\r");
        m_SectionValues.push_back(std::make_pair(first,second));
    }

    /**
    *@brief        写入值(覆盖所有同名key的value)
    */
    void SetValue(const char* pszKey, const char* pszValue)
    {
        bool bFind = false;
        for (PC_ORDERED_MAP::iterator it = m_SectionValues.begin(); it != m_SectionValues.end(); ++it)
        {
            //找到key，覆盖value
            if ((*it).first == pszKey)
            {
                bFind = true;
                (*it).second = pszValue;
            }
        }
        //没找到key，新建key=value
        if(!bFind)    m_SectionValues.push_back(std::make_pair(pszKey,pszValue));
    }

    /**
    *@brief        获取所有值
    */
    void GetAllValues ( std::map<std::string,std::string>& mKeyValue)
    {
        //遍历key
        for (PC_ORDERED_MAP::iterator it = m_SectionValues.begin(); it != m_SectionValues.end(); ++it)
        {
            if("" == (*it).first)
            {
                continue;
            }
            mKeyValue[(*it).first] = (*it).second;
        }
    }

    /**
    *@brief        获取值，如果有多个相同的值，获取最后的那个
    */
    CPCResult<std::string> GetValue(const char* pszKey) 
    {
        CPCResult<std::string> result;
        if ( pszKey == NULL || pszKey[0] == 0)
        {
            return result.SetFail("params err.pszKey == NULL || pszKey[0] == 0");
        }
        bool bFind = false;
        for (PC_ORDERED_MAP::iterator it = m_SectionValues.begin(); it != m_SectionValues.end(); ++it)
        {
            //找到key
            if ((*it).first == pszKey)
            {
                bFind = true;
                result.SetSucc((*it).second);
            }
        }
        if(bFind)    return result;
        else        return result.SetFail("not find sec(%s) > key(%s)",m_SectionName.c_str(),pszKey);
    }

public:
    //section name, 如果某个元素的section name  = ""，则其属于最开头的没有section的段，认为是一个section为空的段
    std::string     m_SectionName;
    //lisst的结构多个key=value，如果key=""，则此行为空行或注释
    PC_ORDERED_MAP  m_SectionValues;
};

/**
*@brief      线程安全的ini配置文件读写类，一个CPCConfig对象读取一个配置文件
*            1.对注释有扩展支持(要求数据和注释不能在同一行)：
*                单行注释： 行首除去空格后以; //  #    <!--内容--> 开始的
*                多行注释： /*    内容    */
/**          2.如果传入的section为空：读写时会忽略掉配置文件中的带section的段(也就是只认为不带section的段才有效)
*            3.如果配置文件中存在相同的key：读时会以最后一个为准:写入时会写入所有相同key的值
*/
class CPCConfig :CPCNoCopyable
{
public:
    explicit CPCConfig(const char* szFilePath = NULL){ SetCfgFilePath(szFilePath); }
    ~CPCConfig(){}

    /**
    *@brief        设置配置文件路径
    *@param        szFilePath[in]    配置文件全路径/配置文件名/NULL
    *                   1.全路径:     按全路径读取文件
    *                   2.配置文件名:  bFromSelfDllPath所在路径，文件名则用传入的这个文件名
    *                   3.传入NULL:   配置文件即为 bFromSelfDllPath全路径.ini
    *@param         bFromSelfDllPath[in]    false：从可执行文件位置加载（默认）；true从动态库文件自身加载
    */
    void SetCfgFilePath(const char* szFilePath, bool bFromSelfDllPath = false)
    {
        m_SectionList.clear();
        m_parseRet.SetSucc();
        
        //全路径
        if(szFilePath != NULL && !CPCFileUtil::PCPathIsFileName(szFilePath))
        {
            m_strFilePath = szFilePath; 
            m_parseRet = OpenAndParse();
            return;
        }

        //读取当前路径
        CPCResult<std::pair<std::string, std::string> > result;
        if (bFromSelfDllPath)
        {
            result = CPCFileUtil::PCGetSelfFileDir();
        }
        else
        {
            result = CPCFileUtil::PCGetExecuteFileDir();
        }

        if(!result)
        {
            m_strFilePath = szFilePath; 
            m_parseRet = OpenAndParse();
            return;
        }
        if(szFilePath == NULL)
        {
            //NULL, 直接 XXX.exe.ini
            std::string strExeFileDir = result.Get().first;
            std::string strExeFileName = result.Get().second;
            if(CPCStrUtil::PCStrIsEndWith(strExeFileName.c_str(), ".exe",true))
            {
                strExeFileName.replace(strExeFileName.end()-4,strExeFileName.end(),".ini");
                m_strFilePath = strExeFileDir + strExeFileName;
            }
            else
            {
                m_strFilePath = strExeFileDir + strExeFileName + ".ini";
            }
        }
        else
        {
            //文件名，以exe位置为基准
            m_strFilePath = result.Get().first + szFilePath;
        }
        m_parseRet = OpenAndParse();
    }

    /**
    *@brief     读取整数
    *@param     pszSec[in]    [section]的值，如果没有section可以传入空
    *@param     pszKey[in]    键
    *@param     nDefault[in]  失败时的默认值
    *@return    返回整数，如果读取失败，返回默认值
    */
    int GetInt(const char * pszSec, const char* pszKey, int nDefault)
    {
        auto ret = this->GetCfg(pszSec, pszKey);
        if(ret)
        {
            return atoi(ret.Get().c_str());
        }
        else
        {
            return nDefault;
        }
    }

	/**
	*@brief     读取布尔值
	*@param     pszSec[in]    [section]的值，如果没有section可以传入空
	*@param     pszKey[in]    键
	*@param     bDefault[in]  失败时的默认值
	*@return    返回整数，如果读取失败，返回默认值
	*/
	bool GetBool(const char * pszSec, const char* pszKey, bool bDefault)
	{
		auto ret = this->GetCfg(pszSec, pszKey);
		if (ret)
		{
			if (strcasecmp(ret.Get().c_str(), "true") == 0)
				return true;
			else if (strcasecmp(ret.Get().c_str(), "false") == 0)
				return false;
			else 
				return bDefault;
		}
		else
		{
			return bDefault;
		}
	}

    /**
    *@brief     读取字符串
    *@param     pszSec[in]      [section]的值，如果没有section可以传入空
    *@param     pszKey[in]      键
    *@param     pszDefault[in]  失败时的默认值
    *@return    返回字符串，如果读取失败，返回默认值
    */
    std::string GetString(const char * pszSec, const char* pszKey, const char* pszDefault)
    {
        auto ret = this->GetCfg(pszSec, pszKey);
        if(ret)
        {
            return ret.Take();
        }
        else
        {
            return pszDefault?pszDefault:"";
        }
    }

    /**
    *@brief     读配置文件
    *@param     pszSec[in]    [section]的值，如果没有section可以传入空
    *@param     pszKey[in]    键
    *@return    是否成功。成功： CPCResult.StrResult1[配置值]
    */
    CPCResult<std::string> GetCfg(const char * pszSec, const char* pszKey)
    {
        CPCGuard<CPCRecMutex> guard(m_CfgMutex);
        CPCResult<std::string> result;
        if ( !m_parseRet)    
        {
            return result.SetFail("%s",m_parseRet.ErrDesc());
        }

        //文件是否存在
        if( ::access(m_strFilePath.c_str(), F_OK) != 0 )
        {
            return result.SetFail("file(%s) can Not Accessable.",m_strFilePath.c_str());
        }
        
        //将内部的配置文件map解析到mKeyValue
        std::string strSection ;
        if(pszSec != NULL)  
            strSection = pszSec;
        for (std::vector<CPCConfigSection>::iterator it = m_SectionList.begin(); it != m_SectionList.end(); ++it)
        {
            //找到section
            if( (*it).m_SectionName == strSection)
            {
                //遍历key
                return (*it).GetValue(pszKey);
            }
        }
        return result.SetFail("frome file(%s) not find  sec(%s) > key(%s)",m_strFilePath.c_str(), pszSec,pszKey);
    }

    /**
    *@brief     写配置文件
    *@param     pszSec[in]      [section]的值，如果没有section可以传入空
    *@param     pszKey[in]      键
    *@param     pszValue[in]    值
    *@return    是否成功。
    */
    CPCResult<> SetCfg(const char * pszSec, const char* pszKey, const char * pszValue)
    {
        CPCGuard<CPCRecMutex> guard(m_CfgMutex);
        if ( !m_parseRet)    return m_parseRet;
        auto result = PushConfig(pszSec, pszKey, pszValue);
        if ( !result)    return result;
        return  FlushToFile();
    }
    
    /**
    *@brief     读取整个Section的所有键值对,不会清空mKeyValue原有数据，但是相同key的数据会被覆盖
    *@param     pszSec[in]       [section]的值，如果没有section可以传入空
    *@param     mKeyValue[out]   输出pszSec的所有键值对
    *@return    是否成功。
    */
    CPCResult<> GetSection(const char * pszSec, std::map<std::string,std::string>& mKeyValue)
    {
        CPCGuard<CPCRecMutex> guard(m_CfgMutex);
        if ( !m_parseRet)    return m_parseRet;

        CPCResult<> result;
        if( ::access(m_strFilePath.c_str(), F_OK) != 0 )
        {
            return result.SetFail("file(%s) can Not Accessable.",m_strFilePath.c_str());
        }

        //将内部的配置文件map解析到mKeyValue
        std::string strSection ;
        if(pszSec != NULL)  
            strSection = pszSec;
        for (std::vector<CPCConfigSection>::iterator it = m_SectionList.begin(); it != m_SectionList.end(); ++it)
        {
            //找到section
            if ((*it).m_SectionName == strSection)
            {
                (*it).GetAllValues(mKeyValue);
            }
        }
        return result;
    }

    /**
    *@brief     读取Section名列表
    *@return    列表。失败为空列表
    */
    std::vector<std::string> GetSectionNameList()
    {
        std::vector<std::string> ret;
        CPCGuard<CPCRecMutex> guard(m_CfgMutex);
        if (m_parseRet)
        {
            for (std::vector<CPCConfigSection>::iterator it = m_SectionList.begin(); it != m_SectionList.end(); ++it)
            {
                //没有任何值的就没必要了
                if(!(*it).m_SectionValues.empty())
                    ret.push_back((*it).m_SectionName);
            }
        }
        return ret;
    }
protected:
    /**
    *@brief     读取配置文件并将其解析到内部的结构
    *@return    是否成功。
    */
    CPCResult<> OpenAndParse()
    {
        //清空
        m_SectionList.clear();

        //按行扫描文件
        CPCConfigSection    section;            
        std::string         strMultiComment = "";
        auto result = CPCFileUtil::PCScanFileLine(m_strFilePath.c_str(),[&strMultiComment,&section,this](std::string &strLine)->CPCResult<>
        {
            CPCResult<> proc;
            std::string pRealLine = CPCStrUtil::PCStrTrim(strLine.c_str(), " \n\t\r");
            size_t nRealLineLen = pRealLine.length();

            //如果多行注释已经开始，则判断是否要结束
            if(!strMultiComment.empty())
            {
                strMultiComment += '\n'; 
                strMultiComment += pRealLine;
                if(std::string::npos != pRealLine.rfind("*/"))
                {
                    //多行注释结束
                    section.AddComment(strMultiComment.c_str());
                    strMultiComment.clear();
                }
                return proc;
            }

            //处理空行&单行注释
            if(    (nRealLineLen == 0 || pRealLine[0] == ';' || pRealLine[0] == '#')    ||
                (nRealLineLen>1 && pRealLine[0] == '/' && pRealLine[1] == '/')        ||    
                (nRealLineLen>3 && pRealLine[0] == '<' && pRealLine[1] == '!')    )
            {
                section.AddComment(pRealLine.c_str());
                return proc;
            }

            //判断是不是多行注释
            if(strMultiComment.empty() && 0 == pRealLine.find("/*")) 
            {
                //多行注释开始
                strMultiComment = pRealLine;
                if(std::string::npos != pRealLine.rfind("*/"))
                {
                    //多行注释结束
                    section.AddComment(strMultiComment.c_str());
                    strMultiComment.clear();
                }
                return proc;
            }

            //处理新的section
            if (pRealLine[0] == '[' && pRealLine[nRealLineLen - 1] == ']')
            {
                //将上一节的数据写进去
                m_SectionList.push_back(section);
                section.Clear();
                
                //当前节名
                pRealLine[nRealLineLen - 1] = '\0';
                section.SetName(pRealLine.c_str() + 1);
                return proc;
            }
            
            //处理实际数据键值对
            section.AddValue(pRealLine.c_str());
            return proc;
        });
        if(!result)
        {
            //文件不存在时，也认为成功，认为是将文件内容解析到一个空的map中
            if(result.ErrCode() == ERROR_FILE_NOT_FOUND || result.ErrCode() == ERROR_PATH_NOT_FOUND)
                return result.SetSucc();
            else return result;
        }

        //最后的多行注释没有闭合
        if(!strMultiComment.empty())
        {
            return result.SetFail("file(%s) parse err: multi comment NOT CLOSED.",m_strFilePath.c_str());
        }
        //将最后一节的数据写进去
        if (!section.IsEmpty())
        {
            m_SectionList.push_back(section);
        }
        return result;
    }
    /**
    *@brief     将配置信息刷入到内部数据结构
    *@return    是否成功。
    */
    CPCResult<> PushConfig(const char * pszSec, const char* pszKey, const char * pszValue)
    {
        CPCResult<> result;
        if ( pszKey == NULL || pszValue == NULL || pszKey[0] == 0 )
        {
            return result.SetFail("params err.pszKey == NULL || pszValue == NULL || pszKey[0] == 0 ");
        }
    
        //从文件映射的map中找
        std::string strSection ;
        if(pszSec != NULL)  
            strSection = pszSec;
        for (std::vector<CPCConfigSection>::iterator it = m_SectionList.begin(); it != m_SectionList.end(); ++it)
        {
            //找到section
            if ((*it).m_SectionName == strSection)
            {
                (*it).SetValue(pszKey, pszValue);
                return result;
            }
        }

        //没找到section，新建section
        CPCConfigSection section;
        section.SetName(strSection.c_str());
        section.SetValue(pszKey, pszValue);
        m_SectionList.push_back(section);
        return result;
    }
    /**
    *@brief     将内部数据结构输出到文件
    *@return    是否成功。
    */
    CPCResult<> FlushToFile()
    {
        CPCResult<> result;
        auto filepath = m_strFilePath.c_str();
        auto result2 = CPCFileUtil::PCSeperatePath(filepath);
        if(!result2)
        {
            return result.SetFail("PCSeperatePath fail:%s",result2.ErrDesc());
        }
        CPCFileUtil::PCCreateDir(result2.Get().first.c_str());

        FILE* fp = fopen(filepath, "w");
        if (NULL == fp)
        {
            return result.SetSystemFail();
        }
        CPCAutoObj autoCfgFile([&fp](){PCCloseFILE(fp);});
        for (std::vector<CPCConfigSection>::iterator itSec = m_SectionList.begin(); itSec != m_SectionList.end(); itSec++)
        {
            if ((*itSec).m_SectionName != "")
            {
                fputs("[", fp);
                fputs((*itSec).m_SectionName.c_str(), fp);
                fputs("]", fp);
                fputs("\n", fp);
            }
            for (CPCConfigSection::PC_ORDERED_MAP::iterator itKV = (*itSec).m_SectionValues.begin(); itKV != (*itSec).m_SectionValues.end(); itKV++)
            {
                if ((*itKV).first != "")
                {
                    fputs((*itKV).first.c_str(), fp);
                    fputs("=", fp);
                }
                fputs((*itKV).second.c_str(), fp);
                fputs("\n", fp);
            }
        }
        fflush(fp);
        return result;
    }

protected:
    //配置文件定义为多个section的列表
    std::vector<CPCConfigSection>  m_SectionList;
    std::string m_strFilePath;
    CPCResult<>   m_parseRet;
    CPCRecMutex m_CfgMutex;
};



#endif    //___PCLIB_S_INI___
